/*
Copyright 2021 LuJun

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
copies of the Software, and to permit persons to whom the Software is furnished 
to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in 
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import {
    Node,
    Prefab,
    instantiate,
    director,
    game,
    Layers,
    Canvas,
    Camera,
    Widget,
    widgetManager,
    renderer,
    error
} from 'cc';
import { UIAnimationType, UIBase } from './ui-base';
import { EVENT_SWITCH_SCENE, event, INDEXPATH_UI, request, EVENT_UI_CLOSED, EVENT_UI_OPENED } from '../core';
import { indexMgr } from '../index/index';

const TAG = 'lcc/ui/ui-manager.ts';

/**
 * @zh
 * 默认画布
 */
const DEFAULT_CANVAS = 'lcc-Canvas';

/**
 * @zh
 * 默认画布优先级
 */
const DEFAULT_CANVAS_PRIORITY = 100000000000000;

/**
 * @zh
 * 优先级最大值
 */
const PRIORITY_MAX = 1000;

/**
 * @zh
 * UI配置
 */
export interface UIConfig {
    /**
     * @zh
     * 预制体文件
     */
    file: string;

    /**
     * @zh
     * 固定优先级
     */
    priority?: number;

    /**
     * @zh
     * 所属画布
     */
    canvas?: string;
}

/**
 * @zh
 * UI数据
 */
interface UIData {
    /**
     * @zh
     * 预制体
     */
    prefab: Prefab;

    /**
     * @zh
     * 配置
     */
    config: UIConfig;

    /**
     * @zh
     * 预加载
     */
    preload?: boolean;

    /**
     * @zh
     * UI对象
     */
    ui: UIBase;
}

/**
 * @zh
 * 画布画布
 */
export interface UICanvas {
    /**
     * @zh
     * 组件对象
     */
    component: Canvas;

    /**
     * @zh
     * UI表
     */
    uis: { [key: string]: UIBase };

    /**
     * @zh
     * 打开表
     */
    opens: { [key: string]: UIBase };

    /**
     * @zh
     * 当前UI优先级
     */
    priority: number;

    /**
     * @zh
     * 最前面的UI对象
     */
    frontUI: UIBase;
}

/**
 * @zh
 * UI快照
 */
export interface UISnapshot {
    /**
     * @zh
     * 场景名
     */
    name: string;

    /**
     * @zh
     * 参数列表
     */
    args: any;
}

/**
 * @zh
 * UI管理器
 */
class UIManager {
    /**
     * @zh
     * 单例对象
     */
    private static _instance: UIManager = null!;

    /**
     * @zh
     * 获得单例对象
     */
    static get instance() {
        if (UIManager._instance === null) {
            UIManager._instance = new UIManager();
        }
        return UIManager._instance;
    }

    /**
     * @zh
     * 当前画布优先级
     */
    private _canvasPriority = DEFAULT_CANVAS_PRIORITY;

    /**
     * @zh
     * 画布映射表
     */
    private _canvasMap: { [key: string]: UICanvas } = Object.create(null);

    /**
     * @zh
     * UI配置表
     */
    private _UIDatas: { [key: string]: UIData } = Object.create(null);

    /**
     * @zh
     * UI操作表
     */
    private _uiOperations: { [key: string]: Function[] } = Object.create(null);

    /**
     * @zh
     * UI栈
     */
    private _uiStack: UISnapshot[] = [];

    /**
     * @zh
     * 获得UI栈
     */
    get uiStack() {
        return this._uiStack;
    }

    /**
     * @zh
     * 构造函数
     */
    private constructor() {
        event.on(EVENT_SWITCH_SCENE, () => {
            this.closeAllUI();
        });
    }

    /**
     * @zh
     * 获得画布
     *
     * @param cname - 画布名称
     */
    getCanvas(cname: string = DEFAULT_CANVAS) {
        let UICanvas = this._canvasMap[cname];
        if (!UICanvas) {
            let canvasNode = new Node(cname);
            canvasNode.layer = Layers.Enum.UI_2D;
            director.getScene()!.addChild(canvasNode);
            game.addPersistRootNode(canvasNode);
            let canvas = canvasNode.addComponent(Canvas);
            let cameraNode = new Node('Camera');
            canvasNode.addChild(cameraNode);
            cameraNode.layer = Layers.Enum.UI_2D;
            let camera = cameraNode.addComponent(Camera);
            camera.projection = renderer.scene.CameraProjection.ORTHO;
            camera.clearFlags = Camera.ClearFlag.DEPTH_ONLY;
            camera.visibility = Layers.BitMask.UI_2D | Layers.BitMask.UI_3D;
            camera.priority = this._canvasPriority;
            canvas.cameraComponent = camera;
            canvas.alignCanvasWithScreen = true;
            let widget = canvasNode.addComponent(Widget);
            widget.alignMode = widgetManager.AlignMode.ON_WINDOW_RESIZE;
            let AlignFlags = widgetManager.AlignFlags;
            widget.alignFlags = AlignFlags.TOP | AlignFlags.BOT | AlignFlags.LEFT | AlignFlags.RIGHT;
            widget.top = 0;
            widget.bottom = 0;
            widget.left = 0;
            widget.right = 0;
            UICanvas = {
                component: canvas,
                uis: {},
                opens: {},
                priority: 1,
                frontUI: null!
            };
            this._canvasMap[cname] = UICanvas;
            this._canvasPriority++;
        }
        return UICanvas;
    }

    /**
     * @zh
     * 设置画布优先级
     *
     * @param nameOrCanvas 名称获得画布对象
     * @param priority 优先级
     */
    setCanvasPriority(nameOrCanvas: string | UICanvas, priority: number) {
        let canvas = typeof nameOrCanvas === 'string' ? this.getCanvas(nameOrCanvas) : nameOrCanvas;
        if (canvas) {
            let camera = canvas.component.getComponentInChildren(Camera);
            if (camera) {
                camera.priority = DEFAULT_CANVAS_PRIORITY + priority;
            }
        }
    }

    /**
     * @zh
     * 获取所有UI
     */
    getAllUIs() {
        return Object.keys(indexMgr.getIndex(INDEXPATH_UI));
    }

    /**
     * @zh
     * 释放指定UI
     *
     * @param uiname - ui名称
     * @param preload - 预加载
     */
    releaseUI(uiname: string, preload: boolean = false) {
        let uiData = this._UIDatas[uiname];
        if (uiData && (!uiData.preload || preload)) {
            this.closeUI(uiname, true);
            this._releaseUI(uiname);
        }
    }

    /**
     * @zh
     * 释放UI数组
     *
     * @param uinames - ui名称数组
     * @param preload - 预加载
     */
    releaseUIs(uinames: string[], preload: boolean = false) {
        for (let uiname of uinames) {
            this.releaseUI(uiname, preload);
        }
    }

    /**
     * @zh
     * 预加载UI
     *
     * @param uiname - ui名称
     */
    async preloadUI(uiname: string) {
        let uiData = await this.getUIData(uiname);
        if (uiData) {
            this._getUI(uiname, uiData, true);
            uiData.preload = true;
        }
    }

    /**
     * @zh
     * 预加载UI数组
     *
     * @param uinames - ui名称数组
     * @param progress - 进度
     */
    async preloadUIs(uinames: string[], progress = (p: number) => {}) {
        for (let i = 0, len = uinames.length; i < len; i++) {
            await this.preloadUI(uinames[i]);
            progress((i + 1) / len);
        }
    }

    /**
     * @zh
     * 获得UI数据
     *
     * @param uiname - UI名称
     */
    private async getUIData(uiname: string) {
        let uiData = this._UIDatas[uiname];
        if (!uiData) {
            let config = indexMgr.getIndex<UIConfig>(INDEXPATH_UI + '/' + uiname);
            if (config) {
                let prefab = await request.get(config.file, { subproto: 'prefab' });
                if (!prefab) {
                    error(TAG, `ui '${uiname}' prefab '${config.file}' not found`);
                } else {
                    uiData = {
                        prefab: prefab,
                        config: config,
                        ui: null
                    };
                    prefab.addRef();
                    this._UIDatas[uiname] = uiData;
                }
            }
        }
        return uiData;
    }

    /**
     * @zh
     * 获得指定UI
     *
     * @param uiname - UI名称
     * @param create - 如果不存在，则创建
     */
    async getUI(uiname: string, create: boolean = false) {
        let uiconf = await this.getUIData(uiname);
        return this._getUI(uiname, uiconf, create);
    }

    /**
     * @zh
     * 获得指定UI
     *
     * @param uiname - UI名称
     * @param create - 如果不存在，则创建
     */
    private _getUI(uiname: string, uiconf: UIData, create: boolean = false) {
        if (uiconf) {
            let ui = uiconf.ui;
            if (!ui && create) {
                let node = instantiate(uiconf.prefab);
                let canvas = this.getCanvas(uiconf.config.canvas);
                canvas.component.node.addChild(node);
                node.active = false;
                ui = (node.getComponent(UIBase) || node.getComponentInChildren(UIBase))!;
                if (!ui) {
                    ui = node.addComponent(UIBase);
                }
                ui.configUI(node, canvas, uiname);
                if (uiconf.config.priority !== null && uiconf.config.priority !== undefined) {
                    ui.setFixedPriority(uiconf.config.priority);
                }
                ui.onUICreate();
                uiconf.ui = ui;
                canvas.uis[uiname] = ui;
            }
            return ui;
        }
    }

    /**
     * @zh
     * 释放指定UI
     *
     * @param uiname - UI名称
     */
    private _releaseUI(uiname: string) {
        let uiData = this._UIDatas[uiname];
        if (uiData) {
            let ui = uiData.ui;
            if (ui) {
                let canvas = ui.canvas;
                ui.onUIRelease();
                ui.rootNode.destroy();
                delete canvas.uis[uiname];
                uiData.prefab.decRef();
                uiData.ui = null!;
                delete this._UIDatas[uiname];
            }
        }
    }

    /**
     * @zh
     * 设置指定UI到最前面
     *
     * @param nameOrUI - 名称或者UI对象
     */
    setFront(nameOrUI: string | UIBase) {
        let ui = typeof nameOrUI === 'string' ? this._UIDatas[nameOrUI]?.ui : nameOrUI;
        if (ui) {
            let canvas = ui.canvas;
            if (ui !== canvas.frontUI) {
                let priority = ui.fixedPriority;
                if (priority === undefined || priority === null) {
                    canvas.priority += 1;
                    priority = canvas.priority;
                }
                let rootNode = ui.rootNode;
                rootNode.zIndex = priority;
                rootNode.parent?.sortChildren();
                if (canvas.frontUI) {
                    canvas.frontUI.onFrontState(false);
                }
                canvas.frontUI = ui;
                canvas.frontUI.onFrontState(true);
            }
            if (canvas.priority >= PRIORITY_MAX) {
                this.sortOpenedUIs(canvas);
            }
        }
    }

    /**
     * @zh
     * 排序打开的UI
     *
     * @param canvas - 画布对象
     */
    private sortOpenedUIs(canvas: UICanvas) {
        let ouis: UIBase[] = [];
        for (let uname in canvas.opens) {
            let ui = canvas.opens[uname];
            if (typeof ui.fixedPriority === 'undefined') {
                ouis.push(ui);
            }
        }
        ouis.sort((a, b) => {
            return a.rootNode.zIndex - b.rootNode.zIndex;
        });
        canvas.priority = ouis.length;
        for (let i = 0, len = ouis.length; i < len; i++) {
            ouis[i].rootNode.zIndex = i + 1;
        }
        canvas.component.node.sortChildren();
    }

    /**
     * @zh
     * 添加UI操作
     *
     * @param uiname - UI名称
     * @param operation - 操作函数
     */
    private addUIOperation(uiname: string, operation: Function) {
        let operations = this._uiOperations[uiname];
        if (!operations) {
            operations = [];
            this._uiOperations[uiname] = operations;
        }
        operations.push(operation);
        if (operations.length === 1) {
            operations[0]();
        }
    }

    /**
     * @zh
     * 执行下个UI操作
     *
     * @param uiname - UI名称
     */
    private nextUIOperation(uiname: string) {
        let operations = this._uiOperations[uiname];
        if (operations.length > 0) {
            operations.shift();
        }
        if (operations.length > 0) {
            operations[0]();
        }
    }

    /**
     * @zh
     * 打开指定UI
     *
     * @param uiname - UI名称
     * @param args - 打开参数
     */
    private async _openUI(uiname: string, ...args: any[]) {
        let ui: UIBase = null!;

        let uiData = await this.getUIData(uiname);
        if (uiData) {
            ui = uiData.ui;
            if (!ui) {
                ui = this._getUI(uiname, uiData, true)!;
                if (ui) {
                    ui.canvas.opens[uiname] = ui;
                }
            }
            if (ui) {
                this.setFront(ui);
                ui.setActive(true);
                ui.onOpen(...args);
                await ui.playUIAnimation(UIAnimationType.OPEN);
            }
        }
        event.emit(EVENT_UI_OPENED + uiname);
        event.emit(EVENT_UI_OPENED, uiname);

        return ui;
    }

    /**
     * @zh
     * 打开指定UI
     *
     * @param uiname - UI名称
     * @param args - 打开参数
     */
    openUI(uiname: string, ...args: any[]) {
        return new Promise<UIBase>((resolve) => {
            this.addUIOperation(uiname, async () => {
                let ui = await this._openUI(uiname, ...args);
                this.nextUIOperation(uiname);
                resolve(ui);
            });
        });
    }

    /**
     * @zh
     * 关闭指定UI
     *
     * @param uiname - UI名称
     * @param release - 释放对象
     */
    private async _closeUI(uiname: string, release: boolean = false) {
        let uiData = this._UIDatas[uiname];
        if (uiData) {
            let ui = uiData.ui;
            if (ui) {
                await ui.playUIAnimation(UIAnimationType.CLOSE);
                let canvas = ui.canvas;
                ui.onClose();
                ui.setActive(false);
                delete canvas.opens[uiname];
                if (ui === canvas.frontUI) {
                    let nfui: UIBase = null!;
                    for (let uin in canvas.opens) {
                        let opui = canvas.opens[uin];
                        if (opui !== canvas.frontUI && (!nfui || nfui.rootNode.zIndex < opui.rootNode.zIndex)) {
                            nfui = opui;
                        }
                    }
                    this.setFront(nfui);
                }
                if (release) {
                    this._releaseUI(uiname);
                }
                event.emit(EVENT_UI_CLOSED + uiname);
                event.emit(EVENT_UI_CLOSED, uiname);
            }
        }
    }

    /**
     * @zh
     * 关闭指定UI
     *
     * @param uiname - UI名称
     * @param release - 释放对象
     */
    closeUI(uiname: string, release: boolean = false) {
        return new Promise<string>((resolve) => {
            this.addUIOperation(uiname, async () => {
                await this._closeUI(uiname, release);
                this.nextUIOperation(uiname);
                resolve(uiname);
            });
        });
    }

    /**
     * @zh
     * 关闭UI数组
     *
     * @param uinames - UI名称数组
     */
    closeUIs(uinames: string[], release: boolean = false) {
        for (let uin of uinames) {
            this.closeUI(uin, release);
        }
    }

    /**
     * @zh
     * 关闭所有UI
     *
     * @param excepts - 排除UI数组
     */
    closeAllUI(excepts?: string[], release: boolean = false) {
        Object.keys(this._UIDatas).forEach((uiname) => {
            if (!excepts || excepts.indexOf(uiname) === -1) {
                this.closeUI(uiname, release);
            }
        });
    }

    /**
     * @zh
     * 使用栈方式打开UI
     *
     * @param uiname - UI名称
     * @param args - 打开参数
     */
    async pushUI(uiname: string, ...args: any[]) {
        if (this._uiStack.length > 0) {
            let uiSnapshot = this._uiStack[this._uiStack.length - 1];
            let ui = await this.getUI(uiSnapshot.name, false);
            let saveArgs = ui?.onSaveUI(); // 更新UI参数
            uiSnapshot.args = saveArgs || uiSnapshot.args;
            await this.closeUI(uiSnapshot.name);
        }
        this._uiStack.push({ name: uiname, args: args });
        let result = await this.openUI(uiname, ...args);
        return result;
    }

    /**
     * @zh
     * 使用栈方式关闭UI
     */
    async popUI() {
        if (this._uiStack.length > 0) {
            await this.closeUI(this._uiStack.pop().name);
        }
        if (this._uiStack.length > 0) {
            let uiSnapshot = this._uiStack[this._uiStack.length - 1];
            if (uiSnapshot.args instanceof Array) {
                await this.openUI(uiSnapshot.name, ...uiSnapshot.args);
            } else {
                await this.openUI(uiSnapshot.name, uiSnapshot.args);
            }
        }
    }

    /**
     * @zh
     * 当按键按下
     *
     * @param keyCode - 按键编码
     */
    onKeyPressed(keyCode: string) {
        let canvasuis: { priority: number; openuis: UIBase[] }[] = [];
        for (let cname in this._canvasMap) {
            let canvas = this._canvasMap[cname];
            let openuis: UIBase[] = [];
            for (let uin in canvas.opens) {
                openuis.push(canvas.opens[uin]);
            }
            canvasuis.push({
                priority: canvas.component.cameraComponent?.priority || 0,
                openuis: openuis
            });
        }
        canvasuis.sort((a, b) => b.priority - a.priority);
        for (let cui of canvasuis) {
            cui.openuis.sort((a, b) => b.rootNode.zIndex - a.rootNode.zIndex);
            for (let ui of cui.openuis) {
                if (ui.onKeyPressed(keyCode)) {
                    return true;
                }
            }
        }
        return false;
    }
}

/**
 * @zh
 * UI管理器对象
 */
export const uiMgr = UIManager.instance;
